
package.cpath = "/opt/xyitech/jieyan-backend-production/xbit/?.so"
local xbit = require 'xbit'
package.cpath = "/usr/nginx/?.so;;"
local json = require 'cjson'
local gh = require 'iwi'

ngx.log(ngx.ERR, ngx.var.server_port)

ngx.log(ngx.ERR, type(ngx.var.server_port))
if ngx.var.server_port ==  '9888' then
    package.path = "/opt/xyitech/jieyan-backend-sandbox/?.lua;;"
    package.cpath = "/opt/xyitech/jieyan-backend-sandbox/?.so;;"
elseif ngx.var.server_port == '11888' then
    package.path = "/opt/xyitech/jieyan-backend-debug/?.lua;;"
    package.cpath = "/opt/xyitech/jieyan-backend-debug/?.so;;"
else
    package.path = "/opt/xyitech/jieyan-backend-production/?.lua;;"
    package.cpath = "/opt/xyitech/jieyan-backend-production/?.so;;"
end

local require = require
local util = require 'util'
local setmetatable = setmetatable
local error = error
local table = table
local ngx = ngx
local os = os
local string = string
local tonumber = tonumber
local server = require "modules.server"
local redis = require "redis"
local plane = require "plane"

local protocal = require 'protocal'
local math = math
local pcall = pcall
local channel_name = "jieyan"
local conf = require "conf"
local pairs = pairs
 _int32_t = 0
 _int16_t = 1
 _float = 2
 _uint32_t = 4
 _uint16_t = 5
 _uint8_t = 6
 _int8_t = 7

ngx.log(ngx.ERR, package.path)
local _fs = util.getChannel("fs_all")
local _ws = util.getChannel("ws_all")






--create connection
local sock = ngx.req.socket(true)

local fid = nil

local landing = {}

local fid_info = {}
-- fid_info = {
--     37 = {
--      ["actual_distance"]  = {}
--     }
   
-- }

local subscribed = false
--create success
if not sock then
  ngx.log(ngx.ERR, "failed to new tcp socket: ", err)
  return ngx.exit(444)
end
 
 
local push = function()
    -- --create redis
    local red = redis:new()
    -- red:set_timeout(5000) -- 1 sec
    local ok, err = red:connect("127.0.0.1", 6379)
    if not ok then
        ngx.log(ngx.ERR, "failed to connect redis: ", err)
        sock:send_close()
        return
    end
 
    --sub
    -- local res, err = red:subscribe(channel_name)
    -- if not res then
    --     ngx.log(ngx.ERR, "failed to sub redis: ", err)
    --     sock:send_close()
    --     return
    -- end

    local res, err = red:subscribe(_fs)
    if not res then
        ngx.log(ngx.ERR, "failed to sub redis: ", err)
        sock:send_close()
        return
    end


    -- loop : read from redis
    while true do
        local res, err = red:read_reply()
        if res then
            local data = res[3]
            -- TODO do data here
            --
            if not sock then
                ngx.log(ngx.ERR, "lost sockect")
                return ngx.exit(444)
            end
            -- sock:settimeout(1000)  -- one second timeout
            -- if filterData(fid, data) then
                ngx.log(ngx.ERR, data)

                local bytes, err = sock:send(data)
                ngx.log(ngx.ERR, bytes)
                ngx.log(ngx.ERR, err)
                if not bytes then
                    -- better error handling
                    ngx.log(ngx.ERR, "failed to send text: ", err)
                    return ngx.exit(444)
                end
            -- end
        else
            if err ~= "timeout" then
                ngx.log(ngx.ERR, "Redis read error: ", err)
                return ngx.exit(444)
            end
        end
    end
end

 function filterData(fid, data)
    local size = _uint32_t
    local msgId = xbit.readBuff(_uint8_t, data, 4)
    local _fid = xbit.readBuff(size, data, 5)
    local len = xbit.readBuff(_uint32_t, data, 0)
    -- ngx.log(ngx.ERR, len)
    -- ngx.log(ngx.ERR, msgId)
    if (msgId == 101) then
        local tb = protocal.toTable(data)
        ngx.log(ngx.ERR, json.encode(tb))
    end
    -- ngx.log(ngx.ERR, fid)
    -- ngx.log(ngx.ERR, _fid)
    -- ngx.log(ngx.ERR, _fid == fid)
    if fid and fid == _fid then
        return data
    else 
        return nil
    end
end

function response(msgId, fid, body, red_p)
    local data, args, final = nil, {}, nil
    local _fid = xbit.readBuff(_uint32_t, body, 1)
    -- ngx.log(ngx.ERR, msgId)
    -- ngx.log(ngx.ERR, fid)
    -- ngx.log(ngx.ERR, _fid)
    if msgId == 7 then
        args = {_uint8_t, 109, _uint32_t, _fid, _uint32_t, os.time()}
        data = xbit.tobit2(unpack(args))
        local prefix = xbit.tobit2(unpack({_uint32_t, #data}))
        final = prefix .. data
    end
    if red_p and final then
        -- ngx.log(ngx.ERR, xbit.tohex(final))
        local byte, err = red_p:publish(_fs, final)
        -- ngx.log(ngx.ERR, byte)
        -- ngx.log(ngx.ERR, err)
    end
end


function calcActualPackageWeight(body, redis, fid_info)
    if not fid_info[fid..""] then
        fid_info[fid..""] = {}
    end
    local info = fid_info[fid..""]
    local fid = xbit.readBuff(_uint32_t, body, 1)
    local msgId = xbit.readBuff(_uint8_t, body, 0)
    -- ngx.log(ngx.ERR, json.encode(info))
    if info and info.mode ~= 4 then
        return
    end
    if not info.mode4Time then
        info.mode4Time = os.time()
    end
    if msgId == 2 then
        local bodyTable = protocal.toTable(body)
        info.volt = bodyTable[4]
        if info.mode4Time + 2 < os.time() and info.mode4Time + 7 > os.time() then
            if info.takeoffVolt then
                info.takeoffVolt = (info.takeoffVolt + info.volt) / 2
            else
                info.takeoffVolt = info.volt
            end
        end
    end

    if msgId == 8 then
        local bodyTable = protocal.toTable(body)
        info.throttle = bodyTable[4]
        -- ngx.log(ngx.ERR, info.throttle)
        if info.mode4Time + 2 < os.time() and os.time() < info.mode4Time + 7 then
            if info.takeoffThrottle then
                info.takeoffThrottle = (info.takeoffThrottle + info.throttle) / 2
            else
                info.takeoffThrottle = info.throttle
            end
        end
    end
    ngx.log(ngx.ERR, json.encode(info))

    if info.mode4Time + 7 < os.time()  and not info.actualWeight then
        info.actualWeight = util.getActualWeight(info.takeoffThrottle or 0, info.volt, info.planeWeight or 11.5)
        redis:set("actualWeight_"..fid, info.actualWeight)
    end
    -- ngx.log(ngx.ERR, json.encode(info))

end

function performLanding(body, redis, db, fid_info)
    local conf = require "conf"
    local order = require "order"
    -- local redis = conf.get_redis()
    -- ngx.log(ngx.ERR, json.encode(fid_info))

    -- local db = conf.get_mysql()
    -- local uid, aid, aids = conf.parse_token("NzUjMTQ4ODc3OTkyMUBqaWV5YW4ueHlpdGVjaC5jb20jMGZlS3ZxUUNHeklBQitIQklDZ2N4R3lHRkNnPQ==", "order")
    local aids = conf.get_aids(0)
    local msgId = xbit.readBuff(_uint8_t, body, 0)
    -- ngx.log(ngx.ERR, msgId)
    if msgId ~= 1 then
        return
    end

    local fid = xbit.readBuff(_uint32_t, body, 1)
    local mode = xbit.readBuff(_uint8_t, body, 10)

    if not fid_info then
        fid_info = {}
    end

    if not fid_info[fid..""] then
        fid_info[fid..""] = {}
    end
        -- ngx.log(ngx.ERR, fid)
    -- ngx.log(ngx.ERR, mode)

    fid_info[fid..""]["mode"] = mode
    if mode ~= 11 then
        landing[fid]= false
        return
    end
    if landing[fid] then
        return
    end
    landing[fid] = true
    local user =  {["spoint_id"]=2,["create_time"]=0,["id"]="75",["role_id"]=1,["aid"]="0",["org_id"]=0, ["name"] = "云平台系统"}
    user.aids = conf.get_aids(0)
    user.orgIds = conf.get_orgIds(0)
    local count, res = order.search(user, {["fid"] = fid, ["state"] = 2, ["token"] = "NzUjMTQ5NTUyMzk0N0BqaWV5YW4ueHlpdGVjaC5jb20jM0lOU3NQa2RTem9STDk2MWczV3o2SzV3QjVjPQ=="})
    ngx.log(ngx.ERR, json.encode(res))
    if count ~= 1 then
        ngx.log(ngx.ERR, "more then 1 record")
        return
    end
    local orderId = res[1].id
    redis:del("order_"..fid)
    redis:del("batteryAfterFlight_"..fid)
    local res = order.update(user, {["id"] = orderId, ["state"] = 5, ["actual_distance"]= fid_info[fid..""]["actual_distance"], ["token"] = "NzUjMTQ5NTUyMzk0N0BqaWV5YW4ueHlpdGVjaC5jb20jM0lOU3NQa2RTem9STDk2MWczV3o2SzV3QjVjPQ=="})
    ngx.log(ngx.ERR, json.encode(res))
end

function saveImage(image, body, redis, db)
    local path = "/opt/xyitech/static/"

    local msgId = xbit.readBuff(_uint8_t, body, 0)
    local fid = xbit.readBuff(_uint32_t, body, 1)
    if msgId ~= 125 then
        return
    end
    local image_id = xbit.readBuff(_uint32_t, body, 9)
    local image_piece_num = xbit.readBuff(_uint8_t, body, 13)
    local image_piece_id = xbit.readBuff(_uint8_t, body, 14) + 1
    ngx.log(ngx.ERR, image_id)
    ngx.log(ngx.ERR, image_piece_num)
    ngx.log(ngx.ERR, image_piece_id)

    if not image[fid..""] or image[fid..""].image_id ~= image_id then
        image[fid..""] = {
            ["image_id"] = image_id,
            ["timestamp"] = os.time(),
            ["count"] = 1,
            ["image_piece_num"] = image_piece_num,
            ["image_piece"] = {
                                [image_piece_id] = string.sub(body, 16, -1)
                            }
        }
    else
        image[fid..""]["count"] = image[fid..""]["count"] + 1
        image[fid..""]["image_piece"][image_piece_id] = string.sub(body, 16, -1)
    end

    for k,v in pairs(image) do
        if v and v.count == image_piece_num then
            local data = util.reduce(image[fid..""]["image_piece"], function (acc, cur) return acc .. cur end, "")
            
            local imageData = util.getImageData(data)
            local geohash = gh.encode(tonumber(imageData.lat_gps), tonumber(imageData.lon_gps), 8)
            ngx.log(ngx.ERR, geohash)
            local file, err = io.open(path .. geohash..".jpg", "w+")
            file:write(data)
            file:close()
            v = nil
        end
        if v and os.time() - v.timestamp > 20 then
            image[k] = nil
        end
    end

end

function gatherData(pack, body, redis, db, fid_info)
    -- ngx.log(ngx.ERR, json.encode(fid_info))

    local msgId = xbit.readBuff(_uint8_t, body, 0)
    local fid = xbit.readBuff(_uint32_t, body, 1)
    if not fid_info[fid..""] then
        fid_info[fid..""] = {}
    end

    -- ngx.log(ngx.ERR, msgId)
    if not util.contains({1,2,3,4,5,8, 111}, msgId) then
        return
    end
    local order, err = redis:get("order_" .. fid)
    if not order or order == ngx.null then
        return
    end
    local ok, _order = pcall(json.decode, order)
    if not ok then
        return
    end

    if msgId == 111 then
        local orderModule = require "order"
        local len1 = xbit.readBuff(_uint8_t, body, 5)
        local user =  {["spoint_id"]=2,["create_time"]=0,["id"]="75",["role_id"]=1,["aid"]="0",["org_id"]=0, ["name"] = "云平台系统"}
        user.aids = conf.get_aids(0)
        user.orgIds = conf.get_orgIds(0)
        local version_number = xbit.readBuff(99, string.sub(body, 7, 6+len1), 0)
        ngx.log(ngx.ERR, version_number)
        local len2 = xbit.readBuff(_uint8_t, body, 6 + len1)
        local hashcode = xbit.readBuff(99, string.sub(body, 8+len1, 7+len1+len2), 0)
        ngx.log(ngx.ERR, hashcode)
        orderModule.update(user, {["id"] = _order.id, ["plane_version_number"] = version_number, ["plane_hashcode"] = hashcode})
    end

    if not pack[fid..""] then
        pack[fid..""] = {}
    end
    local bodyTable = protocal.toTable(body)
    local time = bodyTable[3] or ""
    if not pack[fid..""][time..""] then
        pack[fid..""][time..""] = {}
    end

    if msgId == 3 then
        local speed =util.triCombine(bodyTable[7], bodyTable[8], bodyTable[9])
        local xySpeed = (bodyTable[7]^2 + bodyTable[8]^2)^0.5
        pack[fid..""][time..""]["3"] = {["speed"] = speed, ["xySpeed"] = xySpeed}


        fid_info[fid..""]["actual_distance"] = bodyTable[11]
    elseif msgId == 4 then
        local packWeight = 1
        local res = redis:get("weight_" ..fid)
        if res and res ~= ngx.null then
            packWeight = tonumber(res)
        end
        
        local planeWeight = plane.getWeightByFid(fid)
        -- ngx.log(ngx.ERR, fid)
        -- ngx.log(ngx.ERR, planeWeight)

        fid_info[fid..""]["planeWeight"] = planeWeight
        -- ngx.log(ngx.ERR, json.encode(bodyTable))
        local windSpeedX = util.windSpeedOneDirection(bodyTable[5], planeWeight, packWeight) 
        local windSpeedY = bodyTable[6] / math.abs(bodyTable[6]) * util.windSpeedOneDirection(bodyTable[6], planeWeight, packWeight)
        pack[fid..""][time..""]["4"] = {["relative_alt"] = bodyTable[4], ["windSpeedX"] = windSpeedX, ["windSpeedY"] = windSpeedY, ["hdg"] = bodyTable[7]}
    elseif msgId == 5 then
        pack[fid..""][time..""]["5"] = {["lat_gps"] = bodyTable[5], ["lon_gps"] = bodyTable[6]}
    end

    -- ngx.log(ngx.ERR, json.encode(pack))
    if pack[fid..""][time..""]["3"] and pack[fid..""][time..""]["4"] and pack[fid..""][time..""]["5"] then
        local windSpeedX = tonumber(pack[fid..""][time..""]["4"].windSpeedX) - tonumber(pack[fid..""][time..""]["3"].xySpeed)
        local windSpeedY = tonumber(pack[fid..""][time..""]["4"].windSpeedY)
        local hdg = pack[fid..""][time..""]["4"].hdg
        local windSpeedXFinal = -windSpeedX * math.cos(math.rad(hdg)) + windSpeedY * math.sin(math.rad(hdg))
        local windSpeedYFinal = -windSpeedX * math.sin(math.rad(hdg)) - windSpeedY * math.cos(math.rad(hdg))
        local vals = {}
        vals[#vals + 1] = "fid = " .. fid
        vals[#vals + 1] = "order_id = " .. _order.id
        vals[#vals + 1] = "route_id = " .. _order.route_id
        vals[#vals + 1] = "serial_no = " .. ngx.quote_sql_str(_order.serial_no)
        vals[#vals + 1] = "speed = " .. string.format("%0.2f", pack[fid..""][time..""]["3"].speed)
        vals[#vals + 1] = "relative_alt = " .. string.format("%0.1f", pack[fid..""][time..""]["4"].relative_alt)
        vals[#vals + 1] = "wind_speed_x = " .. string.format("%0.2f", windSpeedXFinal)
        vals[#vals + 1] = "wind_speed_y = " .. string.format("%0.2f", windSpeedYFinal)
        vals[#vals + 1] = "lat_gps = " .. pack[fid..""][time..""]["5"].lat_gps
        vals[#vals + 1] = "lon_gps = " .. pack[fid..""][time..""]["5"].lon_gps
        vals[#vals + 1] = "create_time = " .. os.time()
        
        pack[fid..""][time..""] = nil
        local sql = "INSERT INTO `flight_log` SET " .. table.concat(vals, ",")
        -- ngx.log(ngx.ERR, json.encode(sql))
        local res, err = db:query(sql)

    end
end

local co = ngx.thread.spawn(push)

local red_set = redis:new()
red_set:set_timeout(1000) -- 1 sec
local ok, err = red_set:connect("127.0.0.1", 6379)
if not ok then
    ngx.log(ngx.ERR, "failed to connect redis: ", err)
end

local red_p = redis:new()
red_p:set_timeout(1000) -- 1 sec
local ok, err = red_p:connect("127.0.0.1", 6379)
if not ok then
    ngx.log(ngx.ERR, "failed to connect redis: ", err)
end

local db = conf.get_mysql()
local buff = nil
local readSize = 4
local pack = {}
local image = {}

--main loop
while true do
    -- 获取数据
    if not sock then
        ngx.log(ngx.ERR, "sock not found")
    end
    -- sock:settimeout(5000)  -- one second timeout
    local data, err, partial = sock:receive(readSize)
    -- 如果连接损坏 退出
    if err and err ~= "timeout" then
        ngx.log(ngx.ERR, "sock error: ",  err)
        ngx.log(ngx.ERR, "partial:", partial, #partial)
        -- if partial then
        --     buff = buff .. partial
        -- end
        -- ngx.log(ngx.ERR, err and true)
        -- return ngx.exit(444)
        break
    end
    if data then
        if buff then
            buff = buff .. data
        else 
            buff = data
        end
        -- ngx.log(ngx.ERR, "#buff " .. #buff)
        -- ngx.log(ngx.ERR, "read len" .. xbit.readBuff(_uint32_t, buff, 0))
        local totalLen = xbit.readBuff(_uint32_t, buff, 0)
        if totalLen > 5000 then
            ngx.log(ngx.ERR, "buff wrong data")
            ngx.log(ngx.ERR, totalLen)
        -- -- --     -- local _fid = xbit.readBuff(_uint32_t, buff, 5)
        -- -- --     -- ngx.log(ngx.ERR, _fid)
            
            -- if #buff > 120 then
            --     ngx.log(ngx.ERR, xbit.tohex(buff))
                break
            -- end
        -- -- --     buff = nil
        end
        -- ngx.log(ngx.ERR, totalLen)
        while buff and #buff > 4 and #buff >= totalLen + 4 do
            local len = xbit.readBuff(_uint32_t, buff, 0)
            if len >= 2 then
                -- readSize = len
                local body = string.sub(buff, 5, 4 + len)
                if not fid then
                    fid = xbit.readBuff(_uint32_t, body, 1)
                end

                if not fid_info[fid..""] then
                    fid_info[fid..""] = {}
                end

                local msgId = xbit.readBuff(_uint8_t, body, 0)
                -- ngx.log(ngx.ERR, json.encode(protocal._protocal[msgId]))
                -- ngx.log(ngx.ERR, "msgId: ", msgId)
                -- ngx.log(ngx.ERR, "len: ", len)
                -- ngx.log(ngx.ERR, body)
                if not protocal._protocal[msgId] then
                    ngx.log(ngx.ERR, "msgId not found, msgId:", msgId, " body is ", #body)
                    -- ngx.log(ngx.ERR, xbit.readBuff(_uint32_t, body, 2))
                    -- ngx.log(ngx.ERR, xbit.readBuff(_uint8_t, body, 6))
                    -- ngx.log(ngx.ERR, xbit.readBuff(_uint8_t, body, 7))
                    if msgId ~= 125 and msgId ~= 156 then
                        ngx.log(ngx.ERR, "msg :", xbit.tohex(body))
                    end
                    -- return ngx.exit(444)
                end

                if msgId == 108 then
                    local fid_route, err = red_set:set("fid_route_" .. fid, json.encode(protocal.toTable(body)))
                    if err then
                        ngx.log(ngx.ERR, "failed to set fid_route: ", err)
                    end
                end
                -- ngx.log(ngx.ERR, fid)
                -- ngx.log(ngx.ERR, not fid_info[fid..""])
                
                performLanding(body, red_set, db, fid_info)

                gatherData(pack, body, red_set, db, fid_info)

                response(msgId, fid, body, red_p)

                calcActualPackageWeight(body, red_set, fid_info)

                saveImage(image, body, red_set, db)

                local res, err = red_p:publish(_ws, body)
                if not res then
                    ngx.log(ngx.ERR, "failed to publish redis: ", err)
                    return ngx.exit(444)
                end
                -- readSize = 4
                
            end
            -- local part = math.floor(len / 500) 
            -- for i=1,part do
            --     ngx.log(ngx.ERR, xbit.tohex(string.sub(buff, 1 + (i - 1) * 500, i*500)))
            -- end
            -- ngx.log(ngx.ERR, xbit.tohex(string.sub(buff, 1 + part* 500 , len)))
            buff = string.sub(buff, 5 + len)
        end
    end 
end

ngx.log(ngx.ERR, "socket closing3")
-- sock:send_close()

ngx.thread.kill(co)